﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Pavilion
{
    //vector equation of a line. 
    // <r> = <r0> + t<b>. If you don't understand this, go learn Calc III!
    public struct LineVector
    {
        public Vector2 StartPosition, LineLength;
        public Vector2 EndPosition { get { return StartPosition + LineLength; } set { this.LineLength = value - StartPosition; } }
        public Vector2 CenterPosition { get { return StartPosition + (LineLength / 2); } }

        public LineVector(Vector2 startPosition, Vector2 lineLength)
        {
            this.StartPosition = startPosition;
            this.LineLength = lineLength;
        }

        public override string ToString()
        {
            return "Start(" + StartPosition + "), End(" + EndPosition + ")";
        }

        public float CalculateSlope()
        {
            if (LineLength.Y == 0 || LineLength.X == 0)
                return 0f;

            return LineLength.Y / LineLength.X;
        }

        public float DetermineYPointOfLine(float x)
        {
            if (LineLength.Y == 0 || LineLength.X == 0)
                return StartPosition.Y;

            float slope = LineLength.Y / LineLength.X;

            float returnValue = slope * x - LineLength.X + StartPosition.Y;
            return returnValue;
        }

        public Vector2 DetermineNearestPointToLine(IEnumerable<Vector2> pointCollection)
        {
            Vector2 closestPoint = new Vector2(float.NaN, float.NaN);
            float closestDistance = float.PositiveInfinity;

            foreach (Vector2 point in pointCollection)
            {
                float distance = Vector2.Distance(point, StartPosition) + Vector2.Distance(point, EndPosition);

                if (distance < closestDistance)
                {
                    closestPoint = point;
                    closestDistance = distance;
                }
            }

            return closestPoint;
        }

        public Vector2 FindPointPerpendicularToLine(Vector2 point)
        {
            Vector2 startToPointLineLength = new Vector2(point.X - StartPosition.X, point.Y - StartPosition.Y);     //# Storing vector A->P
            float squaredMagnitudeOfLineLength = (float)(Math.Pow((double)this.LineLength.X, 2) + Math.Pow((double)this.LineLength.Y, 2));

            //The normalized "distance" from StartPosition to your closest point
            float normalizedDistance = Vector2.Dot(startToPointLineLength, this.LineLength) / squaredMagnitudeOfLineLength;

            return StartPosition + LineLength * normalizedDistance;
        }

        public void TransformStartPoint(double angle)
        {
            angle = 360 - (LineVector.FindAngleBetweenLines(EndPosition, StartPosition, EndPosition + new Vector2(5, 0)) - angle);
            float distance = Vector2.Distance(StartPosition, EndPosition);
            double theta = 2 * Math.PI * (angle / 360);

            Vector2 endPosition = EndPosition;
            StartPosition = new Vector2(distance * (float)Math.Cos(theta), distance * (float)Math.Sin(theta)) + endPosition;
            LineLength = endPosition - StartPosition;
        }

        public static Vector2 FindTrianglePoint(LineVector lineVector, double startAngle, double endAngle)
        {
            return FindTrianglePoint(lineVector.StartPosition, lineVector.EndPosition, startAngle, endAngle);
        }

        public static Vector2 FindTrianglePoint(Vector2 a, Vector2 b, double angleA, double angleB)
        {
            double tanA = Math.Tan(angleA * (Math.PI / 180));
            double tanB = Math.Tan(angleB * (Math.PI / 180));

            double x = (tanA * a.X - tanB * b.X + b.Y - a.Y) / (tanA - tanB);
            double y = tanA * x + a.Y - tanA * a.X;

            return new Vector2((float)x, (float)y);
        }

        public static bool Intersects(LineVector firstLine, LineVector secondLine)
        {
            Vector2 deltaStartPosition = secondLine.StartPosition - firstLine.StartPosition;

            float a = MagnitudeOf2dCrossProduct(firstLine.LineLength, secondLine.LineLength);
            float b = MagnitudeOf2dCrossProduct(deltaStartPosition, firstLine.LineLength);

            //special case that lines are colinear
            if (a == 0 && b == 0)
            {
                return IsPointXWithinLineRange(firstLine.StartPosition.X, secondLine) || IsPointXWithinLineRange(firstLine.EndPosition.X, secondLine);
            }
            else
            {
                double t = MagnitudeOf2dCrossProduct(deltaStartPosition, secondLine.LineLength) / a;
                double u = b / a;

                bool isTBetweenZeroAndOne = t <= 1 && t >= 0;
                bool isUBetweenZeroAndOne = u <= 1 && u >= 0;

                return isTBetweenZeroAndOne && isUBetweenZeroAndOne;
            }
        }

        public static bool Intersects(Rectangle rectangle, LineVector line)
        {
            //represent the corners of the rectangle as vectors
            Vector2 rectLeftTop = new Vector2(rectangle.Left, rectangle.Top);
            Vector2 rectRightTop = new Vector2(rectangle.Right, rectangle.Top);
            Vector2 rectLeftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
            Vector2 rectRightBottom = new Vector2(rectangle.Right, rectangle.Bottom);

            //use the vectors to create line segments represented by vectors
            LineVector top = new LineVector(rectLeftTop, rectRightTop - rectLeftTop);
            LineVector bottom = new LineVector(rectLeftBottom, rectRightBottom - rectLeftBottom);
            LineVector left = new LineVector(rectLeftTop, rectLeftBottom - rectLeftTop);
            LineVector right = new LineVector(rectRightTop, rectRightBottom - rectRightTop);

            //test the intersection with each line of the rectangle. 
            return LineVector.Intersects(line, top) ||
                LineVector.Intersects(line, right) ||
                LineVector.Intersects(line, bottom) ||
                LineVector.Intersects(line, left) ||
                IsInsideRectangle(rectangle, line.StartPosition.X, line.StartPosition.Y);
        }

        public static bool Intersects(PolygonBox polygonBox, LineVector lineVector)
        {
            if (polygonBox.Count > 1)
            {
                Vector2 previousPoint = polygonBox.PositionedPointCollection.Last();

                foreach (Vector2 point in polygonBox.PositionedPointCollection)
                {
                    if (Intersects(new LineVector(previousPoint, point - previousPoint), lineVector))
                        return true;
                    previousPoint = point;
                }
            }

            return PolygonBox.IsInsidePolygon(polygonBox, lineVector.StartPosition.X, lineVector.StartPosition.Y);
        }

        static public Vector2 DetermineIntersectionPoint(LineVector lineVector1, LineVector lineVector2)
        {
            bool isParallel = false;

            return DetermineIntersectionPoint(lineVector1, lineVector2, out isParallel);
        }

        static public Vector2 DetermineIntersectionPoint(LineVector lineVector1, LineVector lineVector2, out bool isParallel)
        {
            float dx12 = lineVector1.EndPosition.X - lineVector1.StartPosition.X;
            float dy12 = lineVector1.EndPosition.Y - lineVector1.StartPosition.Y;
            float dx34 = lineVector2.EndPosition.X - lineVector2.StartPosition.X;
            float dy34 = lineVector2.EndPosition.Y - lineVector2.StartPosition.Y;

            float denominator = (dy12 * dx34 - dx12 * dy34);
            isParallel = (denominator == 0);

            if (denominator == 0)
            {
                isParallel = IsPointXWithinLineRange(lineVector1.StartPosition.X, lineVector2) || IsPointXWithinLineRange(lineVector1.EndPosition.X, lineVector2);

                if (isParallel)
                    return lineVector1.CenterPosition;
                else
                    return new Vector2(float.NaN, float.NaN);
            }

            float t1 = ((lineVector1.StartPosition.X - lineVector2.StartPosition.X) * dy34 + (lineVector2.StartPosition.Y - lineVector1.StartPosition.Y) * dx34) / denominator;
            float t2 = ((lineVector2.StartPosition.X - lineVector1.StartPosition.X) * dy12 + (lineVector1.StartPosition.Y - lineVector2.StartPosition.Y) * dx12) / -denominator;

            //Lines intersect if t1 and t2 are between 0 and 1.
            if ((t1 < 0) || (t1 > 1) || (t2 < 0) || (t2 > 1))
                return new Vector2(float.NaN, float.NaN);

            return new Vector2(lineVector1.StartPosition.X + dx12 * t1, lineVector1.StartPosition.Y + dy12 * t1);
        }

        public static double FindAngleBetweenLines(LineVector a, LineVector b)
        {
            return FindAngleBetweenLines(a.StartPosition, a.EndPosition, b.StartPosition, b.EndPosition);
        }

        public static double FindAngleBetweenLines(Vector2 origin, Vector2 a, Vector2 b)
        {
            return FindAngleBetweenLines(origin, a, origin, b);
        }

        public static double FindAngleBetweenLines(Vector2 aStart, Vector2 aEnd, Vector2 bStart, Vector2 bEnd)
        {
            var angle = Math.Atan2(aEnd.Y - aStart.Y, aEnd.X - aStart.X) -
                Math.Atan2(bEnd.Y - bStart.Y, bEnd.X - bStart.X);

            angle = Math.Abs(angle * (180.0 / Math.PI));

            if (angle > 180)
                return 360 - angle;
            else
                return angle;
        }

        public static IEnumerable<LineVector> ObtainLineIntersactions(PolygonBox polygonBox, LineVector lineVector)
        {
            if (polygonBox.Count <= 1)
            {
                if (PolygonBox.IsInsidePolygon(polygonBox, lineVector.StartPosition.X, lineVector.StartPosition.Y))
                    return new List<LineVector>();
                else
                    return null;
            }

            List<LineVector> lineIntersections = new List<LineVector>();

            polygonBox.ExecuteOperationPerLine(new LineExecutionHandler((previous, current) =>
            {
                LineVector polygonLine = new LineVector(previous, current - previous);

                if (Intersects(polygonLine, lineVector))
                    lineIntersections.Add(polygonLine);
            }));

            if (lineIntersections.Count == 0)
                return null;
            else
                return lineIntersections;
        }

        public static bool IsInsideRectangle(Rectangle rectangle, float x, float y)
        {
            return x < rectangle.Right && x > rectangle.Left && y < rectangle.Bottom && y > rectangle.Top;
        }

        public static float MagnitudeOf2dCrossProduct(Vector2 firstVector, Vector2 secondVector)
        {
            return firstVector.X * secondVector.Y - firstVector.Y * secondVector.X;
        }

        public static Vector2 ObtainIntersactionPoint(LineVector lineVector, Rectangle rectangle)
        {
            //represent the corners of the rectangle as vectors
            Vector2 rectLeftTop = new Vector2(rectangle.Left, rectangle.Top);
            Vector2 rectRightTop = new Vector2(rectangle.Right, rectangle.Top);
            Vector2 rectLeftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
            Vector2 rectRightBottom = new Vector2(rectangle.Right, rectangle.Bottom);

            //use the vectors to create line segments represented by vectors
            LineVector top = new LineVector(rectLeftTop, rectRightTop - rectLeftTop);
            LineVector bottom = new LineVector(rectLeftBottom, rectRightBottom - rectLeftBottom);
            LineVector left = new LineVector(rectLeftTop, rectLeftBottom - rectLeftTop);
            LineVector right = new LineVector(rectRightTop, rectRightBottom - rectRightTop);

            Vector2 rectangleCenterPostion = default(Vector2);

            if (PolygonBox.IsInsideRectangle(rectangle, lineVector.StartPosition))
                rectangleCenterPostion = lineVector.StartPosition;
            else if (PolygonBox.IsInsideRectangle(rectangle, lineVector.EndPosition))
                rectangleCenterPostion = lineVector.EndPosition;

            if (Intersects(lineVector, top))
            {
                float slope = -(lineVector.LineLength.X / lineVector.LineLength.Y);
                int height = rectangle.Height / 2;

                return new Vector2(slope * height + rectangleCenterPostion.X, rectangle.Top);
            }
            else if (Intersects(lineVector, bottom))
            {
                float slope = lineVector.LineLength.X / lineVector.LineLength.Y;
                int height = rectangle.Height / 2;

                return new Vector2(slope * height + rectangleCenterPostion.X, rectangle.Bottom);
            }
            else if (Intersects(lineVector, left))
            {
                float slope = -(lineVector.LineLength.Y / lineVector.LineLength.X);
                int width = rectangle.Width / 2;

                return new Vector2(rectangle.Left, slope * width + rectangleCenterPostion.Y);
            }
            else if (Intersects(lineVector, right))
            {
                float slope = lineVector.LineLength.Y / lineVector.LineLength.X;
                int width = rectangle.Width / 2;

                return new Vector2(rectangle.Right, slope * width + rectangleCenterPostion.Y);
            }

            return default(Vector2);
        }

        private static bool IsPointXWithinLineRange(float x, LineVector line)
        {
            bool oddNodes = false;

            if (x < line.StartPosition.X)
                oddNodes = !oddNodes;
            if (x < line.EndPosition.X)
                oddNodes = !oddNodes;

            return oddNodes;
        }
    }
}
